package code.google.jcontainer.support.inject;

import code.google.jcontainer.NoSuchComponentRuntimeException;
import code.google.jcontainer.resolver.FieldAnnotationResolver;
import code.google.jcontainer.resolver.FieldResolveContext;
import code.google.jcontainer.resolver.MethodAnnotationResolver;
import code.google.jcontainer.resolver.MethodResolveContext;
import code.google.jcontainer.resolver.ResolverException;
import code.google.jcontainer.util.ClassUtils;
import code.google.jcontainer.util.MethodUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Properties;
/**
 * \@Inject resolver, inject the dependency AFTER_CONSTRUCT
 *
 * @author <a href="mailto:jfox.young@gmail.com">Young Yang</a>
 */
public class InjectAnnotationResolver implements FieldAnnotationResolver, MethodAnnotationResolver {

    static Log log = LogFactory.getLog(InjectAnnotationResolver.class);

    private static Properties injectMap = new Properties();

    {
        try {
            InputStream in = this.getClass().getClassLoader().getResourceAsStream("inject.properties");
            if(in == null) {
                log.warn("Can not find inject.properties for loading injection constants");
            }
            else {
                injectMap.load(in);
            }
        }
        catch (IOException e) {
            log.warn("Failed to load inject.properties.", e);
        }
    }

    public void resolveFieldAnnotationAfterConstruct(FieldResolveContext fieldResolveContext) throws ResolverException {
        Object component = fieldResolveContext.getComponent();
        Field field = fieldResolveContext.getField();

        Object fieldValue = null;
        Inject injectAnnotation = (Inject)fieldResolveContext.getAnnotation();
        String constantKey = injectAnnotation.constantKey().trim();
        if(constantKey.equals("")) { // inject component
            String injectName = injectAnnotation.name().trim();
            Object refComponent = null;
            if(!injectName.equals("")) { // inject by name
                try {
                    refComponent = fieldResolveContext.getComponent(injectName);
                }
                catch (NoSuchComponentRuntimeException e){
                    throw new InjectRuntimeException("Could not inject component by class: " + field.getType(), e);
                }
            }
            else { // inject by componentClass
                refComponent = fieldResolveContext.getContainer().getComponent(field.getType());
                if(refComponent == null) {
                    throw new InjectRuntimeException("Could not inject component by class: " + field.getType() + " in component: " + fieldResolveContext.getComponentName());
                }
            }
            fieldValue = refComponent;
        }
        else { // inject constant
            String strValue = injectMap.getProperty(constantKey);
            if(strValue == null) {
                fieldValue = null;
                log.warn("inject.properties don't have key: " + constantKey + " while inject " + field);
            }
            else {
                Class<?> fieldType = field.getType();
                if(fieldType.equals(String.class)) {
                    fieldValue = strValue;
                }
                else {
                    try {
                        fieldValue = ClassUtils.newObject(fieldType, strValue);
                    }
                    catch (Exception e) {
                        throw new InjectRuntimeException("Filed to create Object while inject " + field + " with value " + strValue, e);
                    }
                }
            }
        }
        try {
            field.setAccessible(true);
            field.set(component, fieldValue);
        }
        catch (Exception e) {
            throw new ResolverException("Failed to inject " + fieldValue + " to component: " + fieldResolveContext.getComponentName() + " by field:" + field, e);
        }
    }

    public void resolveMethodAnnotationAfterConstruct(MethodResolveContext methodResolveContext) throws ResolverException {
        Object component = methodResolveContext.getComponent();
        Method method = methodResolveContext.getMethod();

        if(!MethodUtils.isSetMethod(method)){
            throw new ResolverException("@Inject can not annotates on none set method: " + method);
        }

        Class<?> parameterType = method.getParameterTypes()[0];
        Inject injectAnnotation = (Inject)methodResolveContext.getAnnotation();

        Object parameterValue = null;
        String constantKey = injectAnnotation.constantKey().trim();
        if(constantKey.equals("")) { // inject by name

            String injectName = injectAnnotation.name().trim();
            Object refComponent = null;
            if(!injectName.equals("")) { // inject by name
                try {
                    refComponent = methodResolveContext.getComponent(injectName);
                }
                catch (NoSuchComponentRuntimeException e){
                    throw new InjectRuntimeException("Could not inject component by class: " + parameterType, e);
                }
            }
            else { // inject by componentClass
                refComponent = methodResolveContext.getContainer().getComponent(parameterType);
                if(refComponent == null) {
                    throw new InjectRuntimeException("Could not inject component by class: " + parameterType + " in component: " + methodResolveContext.getComponentName());
                }
            }
            parameterValue = refComponent;
        }
        else { // inject constant
            String strValue = injectMap.getProperty(constantKey);
            if(strValue == null) {
                parameterValue = null;
                log.warn("inject.properties don't have key: " + constantKey + " while inject " + method);
            }
            else {
                if(parameterType.equals(String.class)) {
                    parameterValue = strValue;
                }
                else {
                    try {
                        parameterValue = ClassUtils.newObject(parameterType, strValue);
                    }
                    catch (Exception e) {
                        throw new InjectRuntimeException("Filed to create Object while inject " + method + " with value " + strValue, e);
                    }
                }
            }
        }
        try {
            method.setAccessible(true);
            method.invoke(component, parameterValue);
        }
        catch (Exception e) {
            throw new ResolverException("Failed to inject " + method + " to component: " + methodResolveContext.getComponentName() + " by method:" + method, e);
        }

    }

    public void resolveMethodAnnotationBeforeMethod(MethodResolveContext methodResolveContext) throws ResolverException {
        // do nothing
    }

    public void resolveMethodAnnotationAfterMethod(MethodResolveContext methodResolveContext) throws ResolverException {
        // do nothing
    }

}
